{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 使用 Hugging Face 和 Milvus 构建 RAG 系统\n",
    "\n",
    "_作者: [张晨](https://github.com/zc277584121)_\n",
    "\n",
    "\n",
    "[Milvus](https://milvus.io/) 是一个广受欢迎的开源向量数据库，为人工智能应用提供高性能和可扩展的向量相似性搜索。在本教程中，我们将向您展示如何使用 Hugging Face 和 Milvus 构建 RAG（检索增强生成）流程。\n",
    "\n",
    "RAG 系统将检索系统与 LLM 相结合。该系统首先使用向量数据库 Milvus 从语料库中检索相关文档，然后使用 Hugging Face 的 LLM 根据检索到的文档生成回答。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## 准备\n",
    "### 依赖关系和环境"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "! pip install --upgrade pymilvus sentence-transformers huggingface-hub langchain_community langchain-text-splitters pypdf tqdm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "> 如果您使用 Google Colab，要启用刚刚安装的依赖项，您可能需要 **重新启动运行时**（单击屏幕顶部的“运行时”菜单，然后从下拉菜单中选择“重新启动会话”） 。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "另外，我们建议您配置您的 [Hugging Face User Access Token](https://huggingface.co/docs/hub/security-tokens)，并将其设置在您的环境变量中，因为我们将使用 Hugging Face Hub 的 LLM 。如果不设置 token 环境变量，您可能会受到较低的请求速率限制。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "os.environ[\"HF_TOKEN\"] = \"hf_...\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 准备数据\n",
    "\n",
    "我们使用 [AI Act PDF](https://artificialintelligenceact.eu/wp-content/uploads/2021/08/The-AI-Act.pdf) 作为我们 RAG 中的私有知识。这是一个针对人工智能的监管框架，具有不同风险级别，对应或多或少的监管。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "vscode": {
     "languageId": "shellscript"
    }
   },
   "outputs": [],
   "source": [
    "%%bash\n",
    "\n",
    "if [ ! -f \"The-AI-Act.pdf\" ]; then\n",
    "    wget -q https://artificialintelligenceact.eu/wp-content/uploads/2021/08/The-AI-Act.pdf\n",
    "fi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们使用 LangChain 的 [`PyPDFLoader`](https://python.langchain.com/v0.1/docs/modules/data_connection/document_loaders/pdf/) 从 PDF 中提取文本，然后将文本拆分为更小的块。默认情况下，我们将块大小设置为 1000，重叠设置为 200，这意味着每个块将有近 1000 个字符，两个块之间的重叠将是 200 个字符。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "108\n"
     ]
    }
   ],
   "source": [
    "from langchain_community.document_loaders import PyPDFLoader\n",
    "\n",
    "loader = PyPDFLoader(\"The-AI-Act.pdf\")\n",
    "docs = loader.load()\n",
    "print(len(docs))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_text_splitters import RecursiveCharacterTextSplitter\n",
    "\n",
    "text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)\n",
    "chunks = text_splitter.split_documents(docs)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "text_lines = [chunk.page_content for chunk in chunks]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 准备 embedding 模型\n",
    "定义一个函数来生成文本 embedding。我们使用 [BGE embedding模型](https://huggingface.co/BAAI/bge-small-en-v1.5) 作为示例，但您也可以将其更改为任何其他 embedding 模型，例如在 [MTEB排行榜](https://huggingface.co/spaces/mteb/leaderboard) 上选择。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sentence_transformers import SentenceTransformer\n",
    "\n",
    "embedding_model = SentenceTransformer(\"BAAI/bge-small-en-v1.5\")\n",
    "\n",
    "def emb_text(text):\n",
    "    return embedding_model.encode([text], normalize_embeddings=True).tolist()[0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "生成测试 embedding 并打印其大小和前几个元素。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "384\n",
      "[-0.07660683244466782, 0.025316666811704636, 0.012505513615906239, 0.004595153499394655, 0.025780051946640015, 0.03816710412502289, 0.08050819486379623, 0.003035430097952485, 0.02439221926033497, 0.0048803347162902355]\n"
     ]
    }
   ],
   "source": [
    "test_embedding = emb_text(\"This is a test\")\n",
    "embedding_dim = len(test_embedding)\n",
    "print(embedding_dim)\n",
    "print(test_embedding[:10])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 将数据加载到 Milvus 中"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 创建 collection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pymilvus import MilvusClient\n",
    "\n",
    "milvus_client = MilvusClient(uri=\"./hf_milvus_demo.db\")\n",
    "\n",
    "collection_name = \"rag_collection\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "> 对于 `MilvusClient` 的参数：\n",
    "> - 将 `uri` 设置为本地文件，例如 `./hf_milvus_demo.db` ，是最方便的方法，因为它会自动使用 [Milvus Lite](https://milvus.io/docs/milvus_lite.md) 将所有数据存储在此文件中。\n",
    "> - 如果您有大量数据，例如超过一百万个向量，您可以在 [Docker 或 Kubernetes](https://milvus.io/docs/quickstart.md) 上设置性能更高的 Milvus 服务器。在此设置中，请使用服务器 uri，例如 `http://localhost:19530` 作为您的 `uri` 。\n",
    "> - 如果您想使用 Milvus 的全托管云服务 [Zilliz Cloud](https://zilliz.com/cloud) ，请调整 `uri` 和 `token`，分别对应 Zilliz Cloud 中 [Public Endpoint 和 Api key](https://docs.zilliz.com/docs/on-zilliz-cloud-console#cluster-details) 。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "检查 collection 是否已存在，如果存在则将其删除。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "if milvus_client.has_collection(collection_name):\n",
    "    milvus_client.drop_collection(collection_name)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用指定参数创建一个新 collection。\n",
    "\n",
    "如果我们不指定任何字段信息，Milvus 会自动创建一个默认的 `id` 字段作为主键，并创建一个 `vector` 字段来存储向量数据。保留的 JSON 字段用于存储未定义 schema 的字段及其值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "milvus_client.create_collection(\n",
    "    collection_name=collection_name,\n",
    "    dimension=embedding_dim,\n",
    "    metric_type=\"IP\",  # Inner product distance\n",
    "    consistency_level=\"Strong\",  # Strong consistency level\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 插入数据\n",
    "迭代文本行，创建 embedding，然后将数据插入到 Milvus 中。\n",
    "\n",
    "这是一个新字段 `text`，它是 collection schema 中的未定义字段。它将自动添加到保留的 JSON 动态字段中，从更高的层次看来，它可被视为普通字段。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Creating embeddings: 100%|████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 429/429 [00:52<00:00,  8.10it/s]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "429"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from tqdm import tqdm\n",
    "\n",
    "data = []\n",
    "\n",
    "for i, line in enumerate(tqdm(text_lines, desc=\"Creating embeddings\")):\n",
    "    data.append({\"id\": i, \"vector\": emb_text(line), \"text\": line})\n",
    "\n",
    "insert_res = milvus_client.insert(collection_name=collection_name, data=data)\n",
    "insert_res[\"insert_count\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 构建 RAG\n",
    "\n",
    "### 检索查询数据\n",
    "\n",
    "让我们具体指定一个有关该语料的问题。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "question = \"What is the legal basis for the proposal?\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在 collection 中搜索问题并检索语义前 3 个匹配项。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "search_res = milvus_client.search(\n",
    "    collection_name=collection_name,\n",
    "    data=[\n",
    "        emb_text(question)\n",
    "    ],  # Use the `emb_text` function to convert the question to an embedding vector\n",
    "    limit=3,  # Return top 3 results\n",
    "    search_params={\"metric_type\": \"IP\", \"params\": {}},  # Inner product distance\n",
    "    output_fields=[\"text\"],  # Return the text field\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们来看看查询的搜索结果\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[\n",
      "    [\n",
      "        \"EN 6  EN 2. LEGAL  BASIS,  SUBSIDIARITY  AND  PROPORTIONALITY  \\n2.1. Legal  basis  \\nThe legal basis for the proposal is in the first place Article 114 of the Treaty on the \\nFunctioning of the European Union (TFEU), which provides for the adoption of measures to \\nensure the establishment and f unctioning of the internal market.  \\nThis proposal constitutes a core part of the EU digital single market strategy. The primary \\nobjective of this proposal is to ensure the proper functioning of the internal market by setting \\nharmonised rules in particular on the development, placing on the Union market and the use \\nof products and services making use of AI technologies or provided as stand -alone AI \\nsystems. Some Member States are already considering national rules to ensure that AI is safe \\nand is developed a nd used in compliance with fundamental rights obligations. This will likely \\nlead to two main problems: i) a fragmentation of the internal market on essential elements\",\n",
      "        0.7412998080253601\n",
      "    ],\n",
      "    [\n",
      "        \"applications and prevent market fragmentation.  \\nTo achieve those objectives, this proposal presents a balanced and proportionate horizontal \\nregulatory approach to AI that is limited to the minimum necessary requirements to address \\nthe risks and problems linked to AI, withou t unduly constraining or hindering technological \\ndevelopment or otherwise disproportionately increasing the cost of placing AI solutions on \\nthe market.  The proposal sets a robust and flexible legal framework. On the one hand, it is \\ncomprehensive and future -proof in its fundamental regulatory choices, including the \\nprinciple -based requirements that AI systems should comply with. On the other hand, it puts \\nin place a proportionate regulatory system centred on a well -defined risk -based regulatory \\napproach that  does not create unnecessary restrictions to trade, whereby legal intervention is \\ntailored to those concrete situations where there is a justified cause for concern or where such\",\n",
      "        0.696428656578064\n",
      "    ],\n",
      "    [\n",
      "        \"approach that  does not create unnecessary restrictions to trade, whereby legal intervention is \\ntailored to those concrete situations where there is a justified cause for concern or where such \\nconcern can reasonably be anticipated in the near future. At the same time, t he legal \\nframework includes flexible mechanisms that enable it to be dynamically adapted as the \\ntechnology evolves and new concerning situations emerge.  \\nThe proposal sets harmonised rules for the development, placement on the market and use of \\nAI systems i n the Union following a proportionate risk -based approach. It proposes a single \\nfuture -proof definition of AI. Certain particularly harmful AI practices are prohibited as \\ncontravening Union values, while specific restrictions and safeguards are proposed in  relation \\nto certain uses of remote biometric identification systems for the purpose of law enforcement. \\nThe proposal lays down a solid risk methodology to define \\u201chigh -risk\\u201d AI systems that pose\",\n",
      "        0.6891457438468933\n",
      "    ]\n",
      "]\n"
     ]
    }
   ],
   "source": [
    "import json\n",
    "\n",
    "retrieved_lines_with_distances = [\n",
    "    (res[\"entity\"][\"text\"], res[\"distance\"]) for res in search_res[0]\n",
    "]\n",
    "print(json.dumps(retrieved_lines_with_distances, indent=4))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用 LLM 获取 RAG 回答\n",
    "\n",
    "在合并到 LLM 提示词之前，我们首先将检索到的文档列表展平为纯字符串。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "context = \"\\n\".join(\n",
    "    [line_with_distance[0] for line_with_distance in retrieved_lines_with_distances]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "定义语言模型的提示词。该提示与从 Milvus 检索到的文档组合在一起。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "PROMPT = \"\"\"\n",
    "Use the following pieces of information enclosed in <context> tags to provide an answer to the question enclosed in <question> tags.\n",
    "<context>\n",
    "{context}\n",
    "</context>\n",
    "<question>\n",
    "{question}\n",
    "</question>\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们使用部署在 Hugging Face 推理服务上的 [Mixtral-8x7B-Instruct-v0.1](https://huggingface.co/mistralai/Mixtral-8x7B-Instruct-v0.1) 根据提示词生成响应。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "from huggingface_hub import InferenceClient\n",
    "\n",
    "repo_id = \"mistralai/Mixtral-8x7B-Instruct-v0.1\"\n",
    "\n",
    "llm_client = InferenceClient(model=repo_id, timeout=120)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "我们设置提示词格式并生成最后的回答。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [],
   "source": [
    "prompt = PROMPT.format(context=context, question=question)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%%\n"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The legal basis for the proposal is Article 114 of the Treaty on the Functioning of the European Union (TFEU), which provides for the adoption of measures to ensure the establishment and functioning of the internal market. The proposal aims to establish harmonized rules for the development, placing on the market, and use of AI systems in the Union following a proportionate risk-based approach.\n"
     ]
    }
   ],
   "source": [
    "answer = llm_client.text_generation(\n",
    "    prompt,\n",
    "    max_new_tokens=1000,\n",
    ").strip()\n",
    "print(answer)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "pycharm": {
     "name": "#%% md\n"
    }
   },
   "source": [
    "恭喜！您已经使用 Hugging Face 和 Milvus 构建了 RAG 流程。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}